#include "SubsystemWoodBlockBehavior.h"
#include "Subsystems/SubsystemGameInfo.h"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Managers/BlocksManager.h"
#include "Terrain/Terrain.h"

#include "Blocks/LeavesBlock.h"
#include "Blocks/WoodBlock.h"

#include "Utils/Utils.h"
#include "Network/PackageReader.h"
#include "Network/PackageWriter.h"
#include "cppcodec/base64_url.hpp"
#include "GameSingleton.h"

#include "Log.h"

namespace PocketSurvival
{
    void SubsystemWoodBlockBehavior::onBlockRemoved(int32_t value, int32_t newValue, int32_t x, int32_t y, int32_t z)
    {
        if (m_gameInfo->environmentBehaviorMode != EnvironmentBehaviorMode::Living || 
            m_leavesToCheck.size() >= 5000 || 
            GameSingleton::blocksManager->blocks[Terrain::ExtractContents(value)]->isBlock<WoodBlock *>() == false)
        {
            return;
        }
        int32_t num = x - 3;
        int32_t num2 = MathUtils::Max(y - 3, 0);
        int32_t num3 = z - 3;
        int32_t num4 = x + 3;
        int32_t num5 = MathUtils::Min(y + 3, 255);
        int32_t num6 = z + 3;

        std::lock_guard<std::mutex> lk(m_leavesToCheckMutex);

        for (int32_t i = num; i <= num4; i++)
        {
            for (int32_t j = num3; j <= num6; j++)
            {
                TerrainChunk *chunkAtCell = m_terrain->getTerrainChunk(i >> 4, j >> 4);
                if (chunkAtCell == nullptr)
                {
                    continue;
                }
                int32_t num7 = TerrainChunk::CalculateCellIndex(i & 0xF, 0, j & 0xF);
                for (int32_t k = num2; k <= num5; k++)
                {
                    int32_t num8 = Terrain::ExtractContents(chunkAtCell->getCellValueFast(num7 + k));
                    if (num8 != 0 && GameSingleton::blocksManager->blocks[num8]->isBlock<LeavesBlock *>() == false)
                    {
                        m_leavesToCheck.insert(Point3(i, k, j));
                    }
                }
            }
        }
    }

    void SubsystemWoodBlockBehavior::onChunkDiscarding(TerrainChunk *chunk)
    {
        int32_t num = chunk->origin.posX - 16;
        int32_t num2 = chunk->origin.posY - 16;
        int32_t num3 = chunk->origin.posX + 32;
        int32_t num4 = chunk->origin.posY + 32;

        std::vector<Point3> removeVec;

        m_leavesToCheckMutex.lock();
        for (const Point3 &item : m_leavesToCheck)
        {
            if (item.posX >= num && item.posX < num3 && item.posZ >= num2 && item.posZ < num4)
            {
                removeVec.push_back(item);
            }
        }
        m_leavesToCheckMutex.unlock();

        if (removeVec.size() > 0)
        {
            decayLeavesIfNeeded(removeVec);
        }
    }

    void SubsystemWoodBlockBehavior::update(float dt)
    {
        m_leavesToCheckMutex.lock();
        if (m_leavesToCheck.size() <= 0 || false == m_timeOfDay->periodicGameTimeEvent(20.0, 0.0))
        {
            m_leavesToCheckMutex.unlock();
            return;
        }

        int32_t num = MathUtils::Min(MathUtils::Max((int32_t)(m_leavesToCheck.size() * 0.1f), 10), 200);
        std::vector<Point3> removeVec;
        if (num >= m_leavesToCheck.size())
        {
            for (const Point3 &point : m_leavesToCheck)
            {
                removeVec.push_back(point);
            }
            m_leavesToCheck.clear();
        }
        else
        {
            for (int32_t index = 0; index < num; ++index)
            {
                removeVec.push_back(*m_leavesToCheck.begin());
                m_leavesToCheck.erase(m_leavesToCheck.begin());
            }
        }
        m_leavesToCheckMutex.unlock();

        decayLeavesIfNeeded(removeVec);
    }

    void SubsystemWoodBlockBehavior::load(const nlohmann::json &json)
    {
        m_gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();

        std::string base64Str;
        Utils::GetJsonValue(json, base64Str, "LeavesToCheck", std::string(""));
        if (base64Str != "")
        {
            std::vector<uint8_t> leavesData = cppcodec::base64_url::decode(base64Str.c_str(), base64Str.length());
            PackageReader reader((const char *)leavesData.data(), leavesData.size());
            uint16_t count = reader.readUInt16();

            Point3 point;
            for (uint16_t index = 0; index < count; ++index)
            {
                reader.readPoint3(point);
                m_leavesToCheck.insert(point);
            }
        }
    }

    void SubsystemWoodBlockBehavior::save(nlohmann::json &json)
    {
        std::lock_guard<std::mutex> lk(m_leavesToCheckMutex);

        uint16_t count = m_leavesToCheck.size();
        if (count > 0)
        {
            PackageWriter writer(m_leavesToCheck.size() * 12 + 4);
            writer.writeUInt16(m_leavesToCheck.size());
            for (const Point3 &point : m_leavesToCheck)
            {
                writer.writePoint3(point);
            }
            std::string base64Str = cppcodec::base64_url::encode(writer.getBuffer(), writer.getPosition());
            json["LeavesToCheck"] = base64Str;
        }
    }

    void SubsystemWoodBlockBehavior::decayLeavesIfNeeded(const std::vector<Point3> &pointVec)
    {
        for (const Point3 &point : pointVec)
        {
            if (GameSingleton::blocksManager->blocks[m_terrain->getCellContents(point)]->isBlock<LeavesBlock *>() == false)
            {
                continue;
            }
            bool flag = false;
            int32_t num = point.posX - 3;
            int32_t num2 = MathUtils::Max(point.posY - 3, 0);
            int32_t num3 = point.posZ - 3;
            int32_t num4 = point.posX + 3;
            int32_t num5 = MathUtils::Min(point.posY + 3, 255);
            int32_t num6 = point.posZ + 3;
            for (int32_t i = num; i <= num4; i++)
            {
                for (int32_t j = num3; j <= num6; j++)
                {
                    TerrainChunk *chunkAtCell = m_terrain->getTerrainChunk(i >> 4, j >> 4);
                    if (chunkAtCell == nullptr)
                    {
                        continue;
                    }
                    int32_t num7 = TerrainChunk::CalculateCellIndex(i & 0xF, 0, j & 0xF);
                    int32_t num8 = num2;
                    while (num8 <= num5)
                    {
                        int32_t num9 = Terrain::ExtractContents(chunkAtCell->getCellValueFast(num7 + num8));
                        if (num9 == 0 || GameSingleton::blocksManager->blocks[num9]->isBlock<WoodBlock *>() == false)
                        {
                            num8++;
                            continue;
                        }
                        goto IL_00e8;
                    }
                }
                continue;
            IL_00e8:
                flag = true;
                break;
            }
            if (!flag)
            {
                m_terrain->changeCell(point.posX, point.posY, point.posZ, 0);
            }
        }
    }

    static const std::string subsystemName = "WoodBlockBehavior";
    const std::string &SubsystemWoodBlockBehavior::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemWoodBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }

} // namespace PocketSurvival
